/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2008 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

XreaL source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "server.h"

#ifdef USE_VOIP
cvar_t         *sv_voip;
#endif

serverStatic_t  svs;			// persistant server info
server_t        sv;				// local server
#if !defined(USE_JAVA)
vm_t           *gvm = NULL;		// game virtual machine
#endif

cvar_t         *sv_fps;			// time rate for running non-clients
cvar_t         *sv_timeout;		// seconds without any message
cvar_t         *sv_zombietime;	// seconds to sink messages after disconnect
cvar_t         *sv_rconPassword;	// password for remote server commands
cvar_t         *sv_privatePassword;	// password for the privateClient slots
cvar_t         *sv_allowDownload;
cvar_t         *sv_maxclients;

cvar_t         *sv_privateClients;	// number of clients reserved for password
cvar_t         *sv_hostname;
cvar_t         *sv_master[MAX_MASTER_SERVERS];	// master server ip address
cvar_t         *sv_reconnectlimit;	// minimum seconds between connect messages
cvar_t         *sv_showloss;	// report when usercmds are lost
cvar_t         *sv_padPackets;	// add nop bytes to messages
cvar_t         *sv_killserver;	// menu system can set to 1 to shut server down
cvar_t         *sv_mapname;
cvar_t         *sv_mapChecksum;
cvar_t         *sv_serverid;
cvar_t         *sv_minRate;
cvar_t         *sv_maxRate;
cvar_t         *sv_minPing;
cvar_t         *sv_maxPing;
cvar_t         *sv_gametype;
cvar_t         *sv_pure;
cvar_t         *sv_floodProtect;
cvar_t         *sv_lanForceRate;	// dedicated 1 (LAN) server forces local client rates to 99999 (bug #491)
cvar_t         *sv_strictAuth;
cvar_t         *sv_banFile;

serverBan_t     serverBans[SERVER_MAXBANS];
int             serverBansCount = 0;

/*
=============================================================================

EVENT MESSAGES

=============================================================================
*/

/*
===============
SV_ExpandNewlines

Converts newlines to "\n" so a line prints nicer
===============
*/
static char    *SV_ExpandNewlines(char *in)
{
	static char     string[1024];
	int             l;

	l = 0;
	while(*in && l < sizeof(string) - 3)
	{
		if(*in == '\n')
		{
			string[l++] = '\\';
			string[l++] = 'n';
		}
		else
		{
			string[l++] = *in;
		}
		in++;
	}
	string[l] = 0;

	return string;
}

/*
======================
SV_ReplacePendingServerCommands

FIXME: This is ugly
======================
*/
#if 0							// unused
static int SV_ReplacePendingServerCommands(client_t * client, const char *cmd)
{
	int             i, index, csnum1, csnum2;

	for(i = client->reliableSent + 1; i <= client->reliableSequence; i++)
	{
		index = i & (MAX_RELIABLE_COMMANDS - 1);
		//
		if(!Q_strncmp(cmd, client->reliableCommands[index], strlen("cs")))
		{
			sscanf(cmd, "cs %i", &csnum1);
			sscanf(client->reliableCommands[index], "cs %i", &csnum2);
			if(csnum1 == csnum2)
			{
				Q_strncpyz(client->reliableCommands[index], cmd, sizeof(client->reliableCommands[index]));
				/*
				   if ( client->netchan.remoteAddress.type != NA_BOT ) {
				   Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
				   }
				 */
				return qtrue;
			}
		}
	}
	return qfalse;
}
#endif

/*
======================
SV_AddServerCommand

The given command will be transmitted to the client, and is guaranteed to
not have future snapshot_t executed before it is executed
======================
*/
void SV_AddServerCommand(client_t * client, const char *cmd)
{
	int             index, i;

	// this is very ugly but it's also a waste to for instance send multiple config string updates
	// for the same config string index in one snapshot
//  if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
//      return;
//  }

	// do not send commands until the gamestate has been sent
	if(client->state < CS_PRIMED)
		return;

	client->reliableSequence++;
	// if we would be losing an old command that hasn't been acknowledged,
	// we must drop the connection
	// we check == instead of >= so a broadcast print added by SV_DropClient()
	// doesn't cause a recursive drop client
	if(client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1)
	{
		Com_Printf("===== pending server commands =====\n");
		for(i = client->reliableAcknowledge + 1; i <= client->reliableSequence; i++)
		{
			Com_Printf("cmd %5d: %s\n", i, client->reliableCommands[i & (MAX_RELIABLE_COMMANDS - 1)]);
		}
		Com_Printf("cmd %5d: %s\n", i, cmd);
		SV_DropClient(client, "Server command overflow");
		return;
	}
	index = client->reliableSequence & (MAX_RELIABLE_COMMANDS - 1);
	Q_strncpyz(client->reliableCommands[index], cmd, sizeof(client->reliableCommands[index]));
}


/*
=================
SV_SendServerCommand

Sends a reliable command string to be interpreted by 
the client game module: "cp", "print", "chat", etc
A NULL client will broadcast to all clients
=================
*/
void QDECL SV_SendServerCommand(client_t * cl, const char *fmt, ...)
{
	va_list         argptr;
	byte            message[MAX_MSGLEN];
	client_t       *client;
	int             j;

	va_start(argptr, fmt);
	Q_vsnprintf((char *)message, sizeof(message), fmt, argptr);
	va_end(argptr);

	// Fix to http://aluigi.altervista.org/adv/q3msgboom-adv.txt
	// The actual cause of the bug is probably further downstream
	// and should maybe be addressed later, but this certainly
	// fixes the problem for now
	if(strlen((char *)message) > 1022)
	{
		return;
	}

	if(cl != NULL)
	{
		SV_AddServerCommand(cl, (char *)message);
		return;
	}

	// hack to echo broadcast prints to console
	if(com_dedicated->integer && !strncmp((char *)message, "print", 5))
	{
		Com_Printf("broadcast: %s\n", SV_ExpandNewlines((char *)message));
	}

	// send the data to all relevent clients
	for(j = 0, client = svs.clients; j < sv_maxclients->integer; j++, client++)
	{
		SV_AddServerCommand(client, (char *)message);
	}
}


/*
==============================================================================

MASTER SERVER FUNCTIONS

==============================================================================
*/

/*
================
SV_MasterHeartbeat

Send a message to the masters every few minutes to
let it know we are alive, and log information.
We will also have a heartbeat sent when a server
changes from empty to non-empty, and full to non-full,
but not on every player enter or exit.
================
*/
#define	HEARTBEAT_MSEC	300*1000
#define	HEARTBEAT_GAME	"XreaL-1"
void SV_MasterHeartbeat(void)
{
	static netadr_t adr[MAX_MASTER_SERVERS][2];	// [2] for v4 and v6 address for the same address string.
	int             i;
	int             res;
	int             netenabled;

	netenabled = Cvar_VariableIntegerValue("net_enabled");

	// "dedicated 1" is for lan play, "dedicated 2" is for inet public play
	if(!com_dedicated || com_dedicated->integer != 2 || !(netenabled & (NET_ENABLEV4 | NET_ENABLEV6)))
		return;					// only dedicated servers send heartbeats

	// if not time yet, don't send anything
	if(svs.time < svs.nextHeartbeatTime)
		return;

	svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC;

	// send to group masters
	for(i = 0; i < MAX_MASTER_SERVERS; i++)
	{
		if(!sv_master[i]->string[0])
			continue;

		// see if we haven't already resolved the name
		// resolving usually causes hitches on win95, so only
		// do it when needed
		if(sv_master[i]->modified || (adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD))
		{
			sv_master[i]->modified = qfalse;

			if(netenabled & NET_ENABLEV4)
			{
				Com_Printf("Resolving %s (IPv4)\n", sv_master[i]->string);
				res = NET_StringToAdr(sv_master[i]->string, &adr[i][0], NA_IP);

				if(res == 2)
				{
					// if no port was specified, use the default master port
					adr[i][0].port = BigShort(PORT_MASTER);
				}

				if(res)
					Com_Printf("%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][0]));
				else
					Com_Printf("%s has no IPv4 address.\n", sv_master[i]->string);
			}

			if(netenabled & NET_ENABLEV6)
			{
				Com_Printf("Resolving %s (IPv6)\n", sv_master[i]->string);
				res = NET_StringToAdr(sv_master[i]->string, &adr[i][1], NA_IP6);

				if(res == 2)
				{
					// if no port was specified, use the default master port
					adr[i][1].port = BigShort(PORT_MASTER);
				}

				if(res)
					Com_Printf("%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][1]));
				else
					Com_Printf("%s has no IPv6 address.\n", sv_master[i]->string);
			}

			if(adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD)
			{
				// if the address failed to resolve, clear it
				// so we don't take repeated dns hits
				Com_Printf("Couldn't resolve address: %s\n", sv_master[i]->string);
				Cvar_Set(sv_master[i]->name, "");
				sv_master[i]->modified = qfalse;
				continue;
			}
		}


		Com_Printf("Sending heartbeat to %s\n", sv_master[i]->string);

		// this command should be changed if the server info / status format
		// ever incompatably changes

		if(adr[i][0].type != NA_BAD)
			NET_OutOfBandPrint(NS_SERVER, adr[i][0], "heartbeat %s\n", HEARTBEAT_GAME);
		if(adr[i][1].type != NA_BAD)
			NET_OutOfBandPrint(NS_SERVER, adr[i][1], "heartbeat %s\n", HEARTBEAT_GAME);
	}
}

/*
=================
SV_MasterShutdown

Informs all masters that this server is going down
=================
*/
void SV_MasterShutdown(void)
{
	// send a hearbeat right now
	svs.nextHeartbeatTime = -9999;
	SV_MasterHeartbeat();

	// send it again to minimize chance of drops
	svs.nextHeartbeatTime = -9999;
	SV_MasterHeartbeat();

	// when the master tries to poll the server, it won't respond, so
	// it will be removed from the list
}


/*
==============================================================================

CONNECTIONLESS COMMANDS

==============================================================================
*/

typedef struct leakyBucket_s leakyBucket_t;
struct leakyBucket_s
{
	netadrtype_t    type;

	union
	{
		byte            _4[4];
		byte            _6[16];
	} ipv;

	int             lastTime;
	signed char     burst;

	long            hash;

	leakyBucket_t  *prev, *next;
};

// This is deliberately quite large to make it more of an effort to DoS
#define MAX_BUCKETS			16384
#define MAX_HASHES			1024

static leakyBucket_t buckets[MAX_BUCKETS];
static leakyBucket_t *bucketHashes[MAX_HASHES];

/*
================
SVC_HashForAddress
================
*/
static long SVC_HashForAddress(netadr_t address)
{
	byte           *ip = NULL;
	size_t          size = 0;
	int             i;
	long            hash = 0;

	switch (address.type)
	{
		case NA_IP:
			ip = address.ip;
			size = 4;
			break;
		case NA_IP6:
			ip = address.ip6;
			size = 16;
			break;
		default:
			break;
	}

	for(i = 0; i < size; i++)
	{
		hash += (long)(ip[i]) * (i + 119);
	}

	hash = (hash ^ (hash >> 10) ^ (hash >> 20));
	hash &= (MAX_HASHES - 1);

	return hash;
}

/*
================
SVC_BucketForAddress

Find or allocate a bucket for an address
================
*/
static leakyBucket_t *SVC_BucketForAddress(netadr_t address, int burst, int period)
{
	leakyBucket_t  *bucket = NULL;
	int             i;
	long            hash = SVC_HashForAddress(address);
	int             now = Sys_Milliseconds();

	for(bucket = bucketHashes[hash]; bucket; bucket = bucket->next)
	{
		switch (bucket->type)
		{
			case NA_IP:
				if(memcmp(bucket->ipv._4, address.ip, 4) == 0)
				{
					return bucket;
				}
				break;

			case NA_IP6:
				if(memcmp(bucket->ipv._6, address.ip6, 16) == 0)
				{
					return bucket;
				}
				break;

			default:
				break;
		}
	}

	for(i = 0; i < MAX_BUCKETS; i++)
	{
		int             interval;

		bucket = &buckets[i];
		interval = now - bucket->lastTime;

		// Reclaim expired buckets
		if(bucket->lastTime > 0 && interval > (burst * period))
		{
			if(bucket->prev != NULL)
			{
				bucket->prev->next = bucket->next;
			}
			else
			{
				bucketHashes[bucket->hash] = bucket->next;
			}

			if(bucket->next != NULL)
			{
				bucket->next->prev = bucket->prev;
			}

			Com_Memset(bucket, 0, sizeof(leakyBucket_t));
		}

		if(bucket->type == NA_BAD)
		{
			bucket->type = address.type;
			switch (address.type)
			{
				case NA_IP:
					Com_Memcpy(bucket->ipv._4, address.ip, 4);
					break;
				case NA_IP6:
					Com_Memcpy(bucket->ipv._6, address.ip6, 16);
					break;
				default:
					break;
			}

			bucket->lastTime = now;
			bucket->burst = 0;
			bucket->hash = hash;

			// Add to the head of the relevant hash chain
			bucket->next = bucketHashes[hash];
			if(bucketHashes[hash] != NULL)
			{
				bucketHashes[hash]->prev = bucket;
			}

			bucket->prev = NULL;
			bucketHashes[hash] = bucket;

			return bucket;
		}
	}

	// Couldn't allocate a bucket for this address
	return NULL;
}

/*
================
SVC_RateLimit
================
*/
static qboolean SVC_RateLimit(leakyBucket_t * bucket, int burst, int period)
{
	if(bucket != NULL)
	{
		int             now = Sys_Milliseconds();
		int             interval = now - bucket->lastTime;
		int             expired = interval / period;
		int             expiredRemainder = interval % period;

		if(expired > bucket->burst)
		{
			bucket->burst = 0;
			bucket->lastTime = now;
		}
		else
		{
			bucket->burst -= expired;
			bucket->lastTime = now - expiredRemainder;
		}

		if(bucket->burst < burst)
		{
			bucket->burst++;

			return qfalse;
		}
	}

	return qtrue;
}

/*
================
SVC_RateLimitAddress

Rate limit for a particular address
================
*/
static qboolean SVC_RateLimitAddress(netadr_t from, int burst, int period)
{
	leakyBucket_t  *bucket = SVC_BucketForAddress(from, burst, period);

	return SVC_RateLimit(bucket, burst, period);
}

/*
================
SVC_Status

Responds with all the info that qplug or qspy can see about the server
and all connected players.  Used for getting detailed information after
the simple info query.
================
*/
static void SVC_Status(netadr_t from)
{
	char            player[1024];
	char            status[MAX_MSGLEN];
	int             i;
	client_t       *cl;
	playerState_t  *ps;
	int             statusLength;
	int             playerLength;
	char            infostring[MAX_INFO_STRING];
	static leakyBucket_t bucket;

/* XXX
	// ignore if we are in single player
	if(Cvar_VariableValue("g_gametype") == GT_SINGLE_PLAYER)
	{
		return;
	}
*/

	// Prevent using getstatus as an amplifier
	if(SVC_RateLimitAddress(from, 10, 1000))
	{
		Com_DPrintf("SVC_Status: rate limit from %s exceeded, dropping request\n", NET_AdrToString(from));
		return;
	}

	// Allow getstatus to be DoSed relatively easily, but prevent
	// excess outbound bandwidth usage when being flooded inbound
	if(SVC_RateLimit(&bucket, 10, 100))
	{
		Com_DPrintf("SVC_Status: rate limit exceeded, dropping request\n");
		return;
	}

	strcpy(infostring, Cvar_InfoString(CVAR_SERVERINFO));

	// echo back the parameter to status. so master servers can use it as a challenge
	// to prevent timed spoofed reply packets that add ghost servers
	Info_SetValueForKey(infostring, "challenge", Cmd_Argv(1));

	status[0] = 0;
	statusLength = 0;

	for(i = 0; i < sv_maxclients->integer; i++)
	{
		cl = &svs.clients[i];
		if(cl->state >= CS_CONNECTED)
		{
			ps = SV_GameClientNum(i);
			Com_sprintf(player, sizeof(player), "%i %i \"%s\"\n", ps->persistant[PERS_SCORE], cl->ping, cl->name);
			playerLength = strlen(player);
			if(statusLength + playerLength >= sizeof(status))
			{
				break;			// can't hold any more
			}
			strcpy(status + statusLength, player);
			statusLength += playerLength;
		}
	}

	NET_OutOfBandPrint(NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status);
}

/*
================
SVC_Info

Responds with a short info message that should be enough to determine
if a user is interested in a server to do a full status
================
*/
void SVC_Info(netadr_t from)
{
	int             i, count;
	char           *gamedir;
	char            infostring[MAX_INFO_STRING];

/* XXX
	// ignore if we are in single player
	if(Cvar_VariableValue("g_gametype") == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
	{
		return;
	}
*/

	/*
	 * Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led
	 * to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory.
	 */

	// A maximum challenge length of 128 should be more than plenty.
	if(strlen(Cmd_Argv(1)) > 128)
		return;

	// don't count privateclients
	count = 0;
	for(i = sv_privateClients->integer; i < sv_maxclients->integer; i++)
	{
		if(svs.clients[i].state >= CS_CONNECTED)
		{
			count++;
		}
	}

	infostring[0] = 0;

	// echo back the parameter to status. so servers can use it as a challenge
	// to prevent timed spoofed reply packets that add ghost servers
	Info_SetValueForKey(infostring, "challenge", Cmd_Argv(1));

	Info_SetValueForKey(infostring, "protocol", va("%i", PROTOCOL_VERSION));
	Info_SetValueForKey(infostring, "hostname", sv_hostname->string);
	Info_SetValueForKey(infostring, "mapname", sv_mapname->string);
	Info_SetValueForKey(infostring, "clients", va("%i", count));
	Info_SetValueForKey(infostring, "sv_maxclients", va("%i", sv_maxclients->integer - sv_privateClients->integer));
	Info_SetValueForKey(infostring, "gametype", va("%i", sv_gametype->integer));
	Info_SetValueForKey(infostring, "pure", va("%i", sv_pure->integer));
	Info_SetValueForKey(infostring, "gamename", GAMENAME_FOR_MASTER);

#ifdef USE_VOIP
	if(sv_voip->integer)
	{
		Info_SetValueForKey(infostring, "voip", va("%i", sv_voip->integer));
	}
#endif

	if(sv_minPing->integer)
	{
		Info_SetValueForKey(infostring, "minPing", va("%i", sv_minPing->integer));
	}
	if(sv_maxPing->integer)
	{
		Info_SetValueForKey(infostring, "maxPing", va("%i", sv_maxPing->integer));
	}
	gamedir = Cvar_VariableString("fs_game");
	if(*gamedir)
	{
		Info_SetValueForKey(infostring, "game", gamedir);
	}

	NET_OutOfBandPrint(NS_SERVER, from, "infoResponse\n%s", infostring);
}

/*
================
SVC_FlushRedirect

================
*/
static void SV_FlushRedirect(char *outputbuf)
{
	NET_OutOfBandPrint(NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf);
}

/*
===============
SVC_RemoteCommand

An rcon packet arrived from the network.
Shift down the remaining args
Redirect all printfs
===============
*/
static void SVC_RemoteCommand(netadr_t from, msg_t * msg)
{
	qboolean        valid;
	char            remaining[1024];

	// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
	// (OOB messages are the bottleneck here)
#define SV_OUTPUTBUF_LENGTH (1024 - 16)
	char            sv_outputbuf[SV_OUTPUTBUF_LENGTH];
	char           *cmd_aux;

	// Prevent using rcon as an amplifier and make dictionary attacks impractical
	if(SVC_RateLimitAddress(from, 10, 1000))
	{
		Com_DPrintf("SVC_Status: rate limit from %s exceeded, dropping request\n", NET_AdrToString(from));
		return;
	}

	if(!strlen(sv_rconPassword->string) || strcmp(Cmd_Argv(1), sv_rconPassword->string))
	{
		static leakyBucket_t bucket;

		// Make DoS via rcon impractical
		if(SVC_RateLimit(&bucket, 10, 1000))
		{
			Com_DPrintf("SVC_Status: rate limit exceeded, dropping request\n");
			return;
		}

		valid = qfalse;
		Com_Printf("Bad rcon from %s: %s\n", NET_AdrToString(from), Cmd_ArgsFrom(2));
	}
	else
	{
		valid = qtrue;
		Com_Printf("Rcon from %s: %s\n", NET_AdrToString(from), Cmd_ArgsFrom(2));
	}

	// start redirecting all print outputs to the packet
	svs.redirectAddress = from;
	Com_BeginRedirect(sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);

	if(!strlen(sv_rconPassword->string))
	{
		Com_Printf("No rconpassword set on the server.\n");
	}
	else if(!valid)
	{
		Com_Printf("Bad rconpassword.\n");
	}
	else
	{
		remaining[0] = 0;

		// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
		// get the command directly, "rcon <pass> <command>" to avoid quoting issues
		// extract the command by walking
		// since the cmd formatting can fuckup (amount of spaces), using a dumb step by step parsing
		cmd_aux = Cmd_Cmd();
		cmd_aux += 4;
		while(cmd_aux[0] == ' ')
			cmd_aux++;
		while(cmd_aux[0] && cmd_aux[0] != ' ')	// password
			cmd_aux++;
		while(cmd_aux[0] == ' ')
			cmd_aux++;

		Q_strcat(remaining, sizeof(remaining), cmd_aux);

		Cmd_ExecuteString(remaining);

	}

	Com_EndRedirect();
}

/*
=================
SV_ConnectionlessPacket

A connectionless packet has four leading 0xff
characters to distinguish it from a game channel.
Clients that are in the game can still send
connectionless packets.
=================
*/
static void SV_ConnectionlessPacket(netadr_t from, msg_t * msg)
{
	char           *s;
	char           *c;

	MSG_BeginReadingOOB(msg);
	MSG_ReadLong(msg);			// skip the -1 marker

	if(!Q_strncmp("connect", (char *)&msg->data[4], 7))
	{
		Huff_Decompress(msg, 12);
	}

	s = MSG_ReadStringLine(msg);
	Cmd_TokenizeString(s);

	c = Cmd_Argv(0);
	Com_DPrintf("SV packet %s : %s\n", NET_AdrToString(from), c);

	if(!Q_stricmp(c, "getstatus"))
	{
		SVC_Status(from);
	}
	else if(!Q_stricmp(c, "getinfo"))
	{
		SVC_Info(from);
	}
	else if(!Q_stricmp(c, "getchallenge"))
	{
		SV_GetChallenge(from);
	}
	else if(!Q_stricmp(c, "connect"))
	{
		SV_DirectConnect(from);
#ifndef STANDALONE
	}
	else if(!Q_stricmp(c, "ipAuthorize"))
	{
		SV_AuthorizeIpPacket(from);
#endif
	}
	else if(!Q_stricmp(c, "rcon"))
	{
		SVC_RemoteCommand(from, msg);
	}
	else if(!Q_stricmp(c, "disconnect"))
	{
		// if a client starts up a local server, we may see some spurious
		// server disconnect messages when their new server sees our final
		// sequenced messages to the old client
	}
	else
	{
		Com_DPrintf("bad connectionless packet from %s:\n%s\n", NET_AdrToString(from), s);
	}
}

//============================================================================

/*
=================
SV_PacketEvent
=================
*/
void SV_PacketEvent(netadr_t from, msg_t * msg)
{
	int             i;
	client_t       *cl;
	int             qport;

	// check for connectionless packet (0xffffffff) first
	if(msg->cursize >= 4 && *(int *)msg->data == -1)
	{
		SV_ConnectionlessPacket(from, msg);
		return;
	}

	// read the qport out of the message so we can fix up
	// stupid address translating routers
	MSG_BeginReadingOOB(msg);
	MSG_ReadLong(msg);			// sequence number
	qport = MSG_ReadShort(msg) & 0xffff;

	// find which client the message is from
	for(i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++)
	{
		if(cl->state == CS_FREE)
		{
			continue;
		}
		if(!NET_CompareBaseAdr(from, cl->netchan.remoteAddress))
		{
			continue;
		}
		// it is possible to have multiple clients from a single IP
		// address, so they are differentiated by the qport variable
		if(cl->netchan.qport != qport)
		{
			continue;
		}

		// the IP port can't be used to differentiate them, because
		// some address translating routers periodically change UDP
		// port assignments
		if(cl->netchan.remoteAddress.port != from.port)
		{
			Com_Printf("SV_PacketEvent: fixing up a translated port for %s[%s]\n", cl->name, NET_AdrToString(from));
			cl->netchan.remoteAddress.port = from.port;
		}

		// make sure it is a valid, in sequence packet
		if(SV_Netchan_Process(cl, msg))
		{
			// zombie clients still need to do the Netchan_Process
			// to make sure they don't need to retransmit the final
			// reliable message, but they don't do any other processing
			if(cl->state != CS_ZOMBIE)
			{
				cl->lastPacketTime = svs.time;	// don't timeout
				SV_ExecuteClientMessage(cl, msg);
			}
		}
		return;
	}

	// if we received a sequenced packet from an address we don't recognize,
	// send an out of band disconnect packet to it
	NET_OutOfBandPrint(NS_SERVER, from, "disconnect");
}


/*
===================
SV_CalcPings

Updates the cl->ping variables
===================
*/
static void SV_CalcPings(void)
{
	int             i, j;
	client_t       *cl;
	int             total, count;
	int             delta;
	playerState_t  *ps;

	for(i = 0; i < sv_maxclients->integer; i++)
	{
		cl = &svs.clients[i];
		if(cl->state != CS_ACTIVE)
		{
			cl->ping = 999;
			continue;
		}
		if(!cl->gentity)
		{
			cl->ping = 999;
			continue;
		}
		if(cl->gentity->r.svFlags & SVF_BOT)
		{
			cl->ping = 0;
			continue;
		}

		total = 0;
		count = 0;
		for(j = 0; j < PACKET_BACKUP; j++)
		{
			if(cl->frames[j].messageAcked <= 0)
			{
				continue;
			}
			delta = cl->frames[j].messageAcked - cl->frames[j].messageSent;
			count++;
			total += delta;
		}
		if(!count)
		{
			cl->ping = 999;
		}
		else
		{
			cl->ping = total / count;
			if(cl->ping > 999)
			{
				cl->ping = 999;
			}
		}

		// let the game dll know about the ping
		ps = SV_GameClientNum(i);
		ps->ping = cl->ping;
	}
}

/*
==================
SV_CheckTimeouts

If a packet has not been received from a client for timeout->integer 
seconds, drop the conneciton.  Server time is used instead of
realtime to avoid dropping the local client while debugging.

When a client is normally dropped, the client_t goes into a zombie state
for a few seconds to make sure any final reliable message gets resent
if necessary
==================
*/
static void SV_CheckTimeouts(void)
{
	int             i;
	client_t       *cl;
	int             droppoint;
	int             zombiepoint;

	droppoint = svs.time - 1000 * sv_timeout->integer;
	zombiepoint = svs.time - 1000 * sv_zombietime->integer;

	for(i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++)
	{
		// message times may be wrong across a changelevel
		if(cl->lastPacketTime > svs.time)
		{
			cl->lastPacketTime = svs.time;
		}

		if(cl->state == CS_ZOMBIE && cl->lastPacketTime < zombiepoint)
		{
			// using the client id cause the cl->name is empty at this point
			Com_DPrintf("Going from CS_ZOMBIE to CS_FREE for client %d\n", i);
			cl->state = CS_FREE;	// can now be reused
			continue;
		}
		if(cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint)
		{
			// wait several frames so a debugger session doesn't
			// cause a timeout
			if(++cl->timeoutCount > 5)
			{
				SV_DropClient(cl, "timed out");
				cl->state = CS_FREE;	// don't bother with zombie state
			}
		}
		else
		{
			cl->timeoutCount = 0;
		}
	}
}


/*
==================
SV_CheckPaused
==================
*/
static qboolean SV_CheckPaused(void)
{
	int             count;
	client_t       *cl;
	int             i;

	if(!cl_paused->integer)
	{
		return qfalse;
	}

	// only pause if there is just a single client connected
	count = 0;
	for(i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++)
	{
		if(cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT)
		{
			count++;
		}
	}

	if(count > 1)
	{
		// don't pause
		if(sv_paused->integer)
			Cvar_Set("sv_paused", "0");
		return qfalse;
	}

	if(!sv_paused->integer)
		Cvar_Set("sv_paused", "1");
	return qtrue;
}

/*
==================
SV_Frame

Player movement occurs as a result of packet events, which
happen before SV_Frame is called
==================
*/
void SV_Frame(int msec)
{
	int             frameMsec;
	int             startTime;

	// the menu kills the server with this cvar
	if(sv_killserver->integer)
	{
		SV_Shutdown("Server was killed");
		Cvar_Set("sv_killserver", "0");
		return;
	}

	if(!com_sv_running->integer)
	{
		// Running as a server, but no map loaded
#ifdef DEDICATED
		// Block until something interesting happens
		Sys_Sleep(-1);
#endif

		return;
	}

	// allow pause if only the local client is connected
	if(SV_CheckPaused())
	{
		return;
	}

	// if it isn't time for the next frame, do nothing
	if(sv_fps->integer < 1)
	{
		Cvar_Set("sv_fps", "10");
	}

	frameMsec = 1000 / sv_fps->integer * com_timescale->value;
	// don't let it scale below 1ms
	if(frameMsec < 1)
	{
		Cvar_Set("timescale", va("%f", sv_fps->integer / 1000.0f));
		frameMsec = 1;
	}

	sv.timeResidual += msec;

	if(!com_dedicated->integer)
		SV_BotFrame(sv.time + sv.timeResidual);

	if(com_dedicated->integer && sv.timeResidual < frameMsec)
	{
		// NET_Sleep will give the OS time slices until either get a packet
		// or time enough for a server frame has gone by
		NET_Sleep(frameMsec - sv.timeResidual);
		return;
	}

	// if time is about to hit the 32nd bit, kick all clients
	// and clear sv.time, rather
	// than checking for negative time wraparound everywhere.
	// 2giga-milliseconds = 23 days, so it won't be too often
	if(svs.time > 0x70000000)
	{
		SV_Shutdown("Restarting server due to time wrapping");
		Cbuf_AddText(va("map %s\n", Cvar_VariableString("mapname")));
		return;
	}
	// this can happen considerably earlier when lots of clients play and the map doesn't change
	if(svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities)
	{
		SV_Shutdown("Restarting server due to numSnapshotEntities wrapping");
		Cbuf_AddText(va("map %s\n", Cvar_VariableString("mapname")));
		return;
	}

	if(sv.restartTime && sv.time >= sv.restartTime)
	{
		sv.restartTime = 0;
		Cbuf_AddText("map_restart 0\n");
		return;
	}

	// update infostrings if anything has been changed
	if(cvar_modifiedFlags & CVAR_SERVERINFO)
	{
		SV_SetConfigstring(CS_SERVERINFO, Cvar_InfoString(CVAR_SERVERINFO));
		cvar_modifiedFlags &= ~CVAR_SERVERINFO;
	}
	if(cvar_modifiedFlags & CVAR_SYSTEMINFO)
	{
		SV_SetConfigstring(CS_SYSTEMINFO, Cvar_InfoString_Big(CVAR_SYSTEMINFO));
		cvar_modifiedFlags &= ~CVAR_SYSTEMINFO;
	}

	if(com_speeds->integer)
	{
		startTime = Sys_Milliseconds();
	}
	else
	{
		startTime = 0;			// quite a compiler warning
	}

	// update ping based on the all received frames
	SV_CalcPings();

	if(com_dedicated->integer)
		SV_BotFrame(sv.time);

	// run the game simulation in chunks
	while(sv.timeResidual >= frameMsec)
	{
		sv.timeResidual -= frameMsec;
		svs.time += frameMsec;
		sv.time += frameMsec;

		// let everything in the world think and move
#if defined(USE_JAVA)
		Java_G_RunFrame(sv.time);
#else
		VM_Call(gvm, GAME_RUN_FRAME, sv.time);
#endif
	}

	if(com_speeds->integer)
	{
		time_game = Sys_Milliseconds() - startTime;
	}

	// check timeouts
	SV_CheckTimeouts();

	// send messages back to the clients
	SV_SendClientMessages();

	// send a heartbeat to the master if needed
	SV_MasterHeartbeat();
}

//============================================================================
